package api

import (
	"context"
	"errors"
	"fmt"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gin-gonic/gin"
	"go-chain-api/contract"
	"go-chain-api/contract/evmAbi"
	"go-chain-api/global"
	models "go-chain-api/internal/model"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"log"
	"math/big"
	"net/http"
	"time"
)

type Swap struct{}

func (s Swap) GetPoolList(c *gin.Context) {
	ChainName := c.MustGet("chainName").(string)
	ChainId := c.MustGet("chainId").(uint)
	pageIndex := c.MustGet("pageIndex").(uint)
	pageSize := c.MustGet("pageSize").(uint)
	swapClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("swap_info")
	var swapInfo []models.SwapInfo
	findOptions := options.Find()
	findOptions.SetSkip(int64(pageIndex))
	findOptions.SetLimit(int64(pageSize))
	data, err := swapClient.Find(context.TODO(), bson.M{"chainId": ChainId, "chainName": ChainName}, findOptions)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "select data failed:" + err.Error(),
		}
		c.JSON(http.StatusBadRequest, response)
		return
	}
	err = data.All(context.Background(), &swapInfo)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "select data.All failed:" + err.Error(),
		}
		c.JSON(http.StatusBadRequest, response)
		return
	}
	response := global.Response{
		Code: http.StatusOK,
		Data: swapInfo,
		Msg:  "success",
	}
	c.JSON(http.StatusOK, response)
}

func (s Swap) GetPoolTxList(c *gin.Context) {
	pageIndex := c.MustGet("pageIndex").(uint)
	pageSize := c.MustGet("pageSize").(uint)

	startTime := c.Query("startTime")
	endTime := c.Query("endTime")
	filter := bson.D{}
	if startTime != "" && endTime != "" {
		// 定义时间字符串的格式
		const layout = "2006-01-02 15:04:05"
		// 解析时间字符串到UTC时区
		startTimeUTC, err := time.ParseInLocation(layout, startTime, time.UTC)
		if err != nil {
			panic(err)
		}
		startTimestamp := startTimeUTC.Unix()

		endTimeUTC, err := time.ParseInLocation(layout, endTime, time.UTC)
		if err != nil {
			panic(err)
		}
		endTimestamp := endTimeUTC.Unix()
		filter = bson.D{{"$match", bson.D{{"chainName", "BSC"}, {"chainId", 56}, {"timestamp", bson.M{"$gte": startTimestamp, "$lte": endTimestamp}}}}}
	} else {
		filter = bson.D{{"$match", bson.D{{"chainName", "BSC"}, {"chainId", 56}}}}
	}
	// 获取聚合管道

	pipeline := mongo.Pipeline{
		filter,
		bson.D{{"$lookup", bson.D{
			{"from", "swap_info"},
			{"localField", "tokenAddress"},
			{"foreignField", "address"},
			{"as", "swapDetails"},
		}}},
		bson.D{{"$unwind", bson.D{{"path", "$swapDetails"}, {"preserveNullAndEmptyArrays", true}}}},
		bson.D{{"$project", bson.D{
			{"_id", 0},
			{"chainName", 1},
			{"from", 1},
			{"to", 1},
			{"sender", 1},
			{"amount0In", 1},
			{"amount1In", 1},
			{"amount0Out", 1},
			{"amount1Out", 1},
			{"timestamp", 1},
			{"value", 1},
			{"price", 1},
			{"type", 1},
			{"token0Name", "$swapDetails.token0Name"},
			{"token1Name", "$swapDetails.token1Name"},
		}}},
		// 添加$skip和$limit步骤来实现分页
		bson.D{{"$skip", int64(pageIndex)}},
		bson.D{{"$limit", int64(pageSize)}},
	}

	swapTxClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("swap_transaction_info")

	cursor, err := swapTxClient.Aggregate(context.TODO(), pipeline)
	if err != nil {
		return
	}

	var results []models.PoolTxList
	if err = cursor.All(context.TODO(), &results); err != nil {
		log.Fatal(err)
	}
	documents, err := swapTxClient.CountDocuments(context.TODO(), bson.M{"chainName": "BSC", "chainId": 56})
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	// 关闭游标和客户端连接
	if err = cursor.Close(context.TODO()); err != nil {
		log.Fatal(err)
	}

	data := global.Page{
		Total: int64(int(documents)),
		List:  results,
	}

	response := global.Response{
		Code: http.StatusOK,
		Data: data,
		Msg:  "success",
	}
	c.JSON(http.StatusOK, response)

}

// 添加底池信息
func (s Swap) CreateSwapInfo(c *gin.Context) {

	var swapInfo models.SwapInfo

	err := c.ShouldBindJSON(&swapInfo)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "missing param:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	var scanConfig models.ScanConfig
	configClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("scan_config")
	err = configClient.FindOne(context.TODO(), bson.M{"chainId": swapInfo.ChainId, "chainName": swapInfo.ChainName}).Decode(&scanConfig)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "select data failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}

	client, err := ethclient.Dial(scanConfig.RpcUrl)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "connect rpcUrl failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}

	//获取底池地址
	pair, err := contract.CallContractMethod(client, common.HexToAddress(scanConfig.FactoryAddress), evmAbi.PANCAKE_V2_FACTORY, "getPair", common.HexToAddress(swapInfo.Token0Address), common.HexToAddress(swapInfo.Token1Address))
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "CallContractMethod failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}

	pairAddress := pair[0].(common.Address).Hex()
	swapClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("swap_info")
	err = swapClient.FindOne(context.TODO(), bson.M{"address": pairAddress}).Decode(&swapInfo)
	if err != nil && !errors.Is(err, mongo.ErrNoDocuments) {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "FindOne failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	if swapInfo.Id != "" {
		response := global.Response{
			Code: http.StatusOK,
			Data: nil,
			Msg:  "pair info is exist",
		}
		c.JSON(http.StatusOK, response)
		return

	}

	token0Info, err := createToken(swapInfo.ChainName, swapInfo.ChainId, swapInfo.Token0Address, client)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "insert data Token0Address error:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}

	token1Info, err := createToken(swapInfo.ChainName, swapInfo.ChainId, swapInfo.Token1Address, client)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "insert data Token1Address error:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}

	swapInfo.Address = pairAddress
	swapInfo.Factory = scanConfig.FactoryAddress
	swapInfo.Token0Name = token0Info.Symbol
	swapInfo.Token1Name = token1Info.Symbol

	_, err = swapClient.InsertOne(context.TODO(), swapInfo)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "insert data swapInfo error:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}

	response := global.Response{
		Code: http.StatusOK,
		Data: swapInfo,
		Msg:  "Success",
	}
	c.JSON(http.StatusOK, response)

}

func createToken(chainName string, chainId uint, tokenAddress string, client *ethclient.Client) (models.TokenInfo, error) {
	var tokenInfo models.TokenInfo
	tokenClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("token")
	err := tokenClient.FindOne(context.TODO(), bson.M{"address": tokenAddress}).Decode(&tokenInfo)
	if errors.Is(err, mongo.ErrNoDocuments) {
		tokenName, err := contract.CallContractMethod(client, common.HexToAddress(tokenAddress), evmAbi.UNISWAP_V2_PAIR_ABI, "name")
		if err != nil {
			return models.TokenInfo{}, err
		}
		tokenSym, err := contract.CallContractMethod(client, common.HexToAddress(tokenAddress), evmAbi.UNISWAP_V2_PAIR_ABI, "symbol")
		if err != nil {
			return models.TokenInfo{}, err
		}
		tokenDec, err := contract.CallContractMethod(client, common.HexToAddress(tokenAddress), evmAbi.UNISWAP_V2_PAIR_ABI, "decimals")
		if err != nil {
			return models.TokenInfo{}, err
		}
		tokenTot, err := contract.CallContractMethod(client, common.HexToAddress(tokenAddress), evmAbi.UNISWAP_V2_PAIR_ABI, "totalSupply")
		if err != nil {
			return models.TokenInfo{}, err
		}
		tokenInfo.Address = tokenAddress
		tokenInfo.ChainName = chainName
		tokenInfo.ChainID = int64(chainId)
		tokenInfo.Name = tokenName[0].(string)
		tokenInfo.Symbol = tokenSym[0].(string)
		tokenInfo.Decimals = uint(tokenDec[0].(uint8))
		tokenInfo.TotalSupply = tokenTot[0].(*big.Int).String()

		_, err = tokenClient.InsertOne(context.TODO(), tokenInfo)
		if err != nil {
			return models.TokenInfo{}, err
		}
		return tokenInfo, nil
	}
	return models.TokenInfo{}, err
}
